package com.transfiguro.visitors;

import com.transfiguro.datamodel.javamodels.ClassModel;
import com.transfiguro.datamodel.ClassType;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.body.ClassOrInterfaceDeclaration;
import japa.parser.ast.type.ClassOrInterfaceType;
import japa.parser.ast.visitor.VoidVisitorAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class provides functions to visit nodes in an {@link ClassOrInterfaceDeclaration}
 * and build a {@link ClassModel} out of it
 */
public class ClassVisitor extends VoidVisitorAdapter {
    private final Logger logger = LoggerFactory.getLogger(ClassVisitor.class);
    private ClassModel model;
    private MethodVisitor methodVisitor;
    private CompilationUnit compilationUnit;
    private ConstructorVisitor constructorVisitor;
    private ClassVarVisitor classVarVisitor;
    private ImportsVisitor importsVisitor;

    /**
     * Constructor
     *
     * @param unit The <code>CompilationUnit</code> that will be used to access different items
     *             in the given .java class
     */
    public ClassVisitor(final CompilationUnit unit) {
        model = new ClassModel();
        compilationUnit = unit;
        methodVisitor = new MethodVisitor();
        constructorVisitor = new ConstructorVisitor();
        classVarVisitor = new ClassVarVisitor();
        importsVisitor = new ImportsVisitor();
    }

    /**
     * This method visits nodes in a given {@link ClassOrInterfaceDeclaration} and
     * builds a {@link ClassModel}
     *
     * @param ciDeclaration The <code>ClassOrInterfaceDeclaration</code> to parse
     * @param arg           Optional argument that can be used to pass anything into the function
     */
    @Override
    public void visit(final ClassOrInterfaceDeclaration ciDeclaration, final Object arg) {
        logger.info("{}", ciDeclaration.getName());
        model.setClassName(ciDeclaration.getName());
        if (!(ciDeclaration.getName().contains("abstract"))) {
            model.setClassType(ciDeclaration.isInterface() ? ClassType.INTERFACE : ClassType.BASECLASS);
        }

        if (ciDeclaration.getImplements() != null) {
            for (final ClassOrInterfaceType ciDec : ciDeclaration.getImplements()) {
                model.addParent(ciDec.getName());
            }
        }

        if (ciDeclaration.getExtends() != null) {
            for (final ClassOrInterfaceType ciDec : ciDeclaration.getExtends()) {
                model.addParent(ciDec.getName());
            }
        }


        if (!ciDeclaration.isInterface()) {
            model.setAccessModifier(ciDeclaration.getModifiers());

            model.setImports(importsVisitor.getImports(compilationUnit));

            classVarVisitor.getVars(ciDeclaration);
            model.setVariables(classVarVisitor.getVariableList());

            methodVisitor.visit(compilationUnit, false);
            model.setMethods(methodVisitor.getMethodList());

            constructorVisitor.visit(compilationUnit, null);
            model.getMethods().add(constructorVisitor.getMethod());
        } else {
            model.setAccessModifier(ciDeclaration.getModifiers());
            model.setImports(importsVisitor.getImports(compilationUnit));
            methodVisitor.visit(compilationUnit, true);
            model.setMethods(methodVisitor.getMethodList());
        }
    }

    /**
     * Getter for <code>model</code>
     *
     * @return The <code>ClassModel</code> created by visiting the various nodes in the given
     *         <code>ClassOrInterfaceDeclaration</code>
     */
    public ClassModel getModel() {
        return model;
    }
}
